﻿using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
    /// <summary>Collection of <see cref="IDbColumn"/> objects.</summary>
    [Serializable]
    public sealed class DbColumnCollection : IEnumerable, IEnumerable<IDbColumn>
    {
        #region Members.

        List<IDbColumn> list;

        #endregion

        #region CTor.

        /// <summary>
        /// Initializes a new instance of the <b>DbColumnCollection</b> class.
        /// </summary>
        public DbColumnCollection()
        {
            this.list = new List<IDbColumn>();
        }

        /// <summary>
        /// Initializes a new instance of the <b>DbColumnCollection</b> class that is empty and has the specified initial capacity.
        /// </summary>
        /// <param name="capacity">The number of elements that the new list can initially store.</param>
        public DbColumnCollection(int capacity)
        {
            this.list = new List<IDbColumn>(capacity);
        }

        #endregion

        #region Properties and indexer.

        /// <summary>
        /// Gets the column at the specified index.
        /// </summary>
        public IDbColumn this[int index]
        {
            get { return this.list[index]; }
        }

        /// <summary>
        /// Gets the total number of columns contained in the collection.
        /// </summary>
        public int Count
        {
            get { return this.list.Count; }
        }

        #endregion

        #region Read-only management.

        /// <summary>Gets a value indicating whether the collection is read-only, ie. whether its contents may be modified.</summary>
        public bool IsReadOnly { get; private set; }

        /// <summary>Makes the collection read-only, ie. items may be no longer added to or removed from it.</summary>
        /// <remarks><b>Recommendation</b>: call <b>MakeReadOnly()</b> method once all required columns have been added to collection.</remarks>
        public void MakeReadOnly()
        {
            this.IsReadOnly = true;
        }

        private void EnsureNotReadOnly()
        {
            if (this.IsReadOnly)
                throw new InvalidOperationException(Messages.DbColumnCollection_ErrorBecauseItsReadOnly);
        }

        #endregion

        #region Methods which modify internal state. Removed all accept Add().

        /// <summary>
        /// Adds a column to the end of the collection.
        /// Does nothing if a column with the same alias already exists in the collection.
        /// </summary>
        /// <param name="column">Column. Cannot be <b>null</b>.</param>
        /// <remarks>Consider calling <see cref="MakeReadOnly"/> once all required columns are added to collection.</remarks>
        public void Add(IDbColumn column)
        {
            EnsureNotReadOnly();
            if (column == null)
                throw new ArgumentNullException("column", Messages.DbColumnCollection_ColumnMayNotBeNull);

            if (GetByAlias(column.Alias) == null)
                this.list.Add(column);
        }

        #endregion

        #region ToArray, IndexOf and Get* methods.

        /// <summary>
        /// Gets the column at the specified index.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Column at the specified index.</returns>
        public IDbColumn GetAt(int index)
        {
            return this.list[index];
        }

        /// <summary>
        /// Gets the column with the specified alias.
        /// </summary>
        /// <param name="alias">Column alias.</param>
        /// <returns>Column or <b>null</b> if it doesn't exist.</returns>
        public IDbColumn GetByAlias(string alias)
        {
            if (alias == null)
                return null;

            foreach (IDbColumn candidate in this)
            {
                if (candidate.Alias == alias)
                    return candidate;
            }

            return null;
        }

        /// <summary>
        /// Returns the first column in the collection with the specified name.
        /// </summary>
        /// <param name="name">Column name.</param>
        /// <returns>Column or <b>null</b> if it doesn't exist.</returns>
        public IDbColumn GetByColumnName(string name)
        {
            if (name == null)
                return null;

            foreach (IDbColumn candidate in this)
            {
                if (candidate.ColumnName == name)
                    return candidate;
            }

            return null;
        }

        /// <summary>
        /// Returns the first column in the collection with the specified entity class property name.
        /// </summary>
        /// <param name="propertyName">Entity class property mapped to this column.</param>
        /// <returns>Column or <b>null</b> if it doesn't exist.</returns>
        public IDbColumn GetByPropertyName(string propertyName)
        {
            if (propertyName == null)
                return null;

            foreach (IDbColumn candidate in this)
            {
                if (candidate.PropertyName == propertyName)
                    return candidate;
            }

            return null;
        }

        /// <summary>
        /// Copies the elements of the collection to a new array.
        /// </summary>
        /// <returns>Array of columns.</returns>
        public IDbColumn[] ToArray()
        {
            return this.list.ToArray();
        }

        /// <summary>
        /// Returns the zero-based index of the specified column.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <returns>The zero-based index of the first occurrence of the specified column within the collection, if found; otherwise, -1.</returns>
        public int IndexOf(string columnName)
        {
            for (int i = 0; i < this.list.Count; i++)
            {
                if (this.list[i].ColumnName == columnName)
                    return i;
            }

            return -1;
        }

        #endregion

        #region IEnumerable.

        /// <summary>
        /// Gets an enumerator that can iterate through DbColumnCollection.
        /// </summary>
        public IEnumerator GetEnumerator()
        {
            return this.list.GetEnumerator();
        }

        #endregion

        #region IEnumerable<IDbColumn> Members

        /// <summary>
        /// Gets an enumerator that can iterate through DbColumnCollection.
        /// </summary>
        IEnumerator<IDbColumn> IEnumerable<IDbColumn>.GetEnumerator()
        {
            return this.list.GetEnumerator();
        }

        #endregion
    }
}